using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.Xml;
using System.Reflection;
using System.IO;
using System.Xml.Schema;
using System.Threading;
using System.Diagnostics;
using Accountability4net;
using System.Drawing;
using System.Drawing.Imaging;
using System.ComponentModel;
using System.Windows.Forms;
using Accountability4net.FileMonengineProvider;


namespace Accountability4net.UnitTests
{
    [TestFixture]
    public class FileTests
    {
        private MonengineProviderBase provider_;

        private MonengineProviderBase Provider
        {
            get
            {
                if (provider_ == null)
                {
                    provider_ = MonengineManager.GetProvider("FileMonengineProvider");
                }
                return provider_;
            }
            set
            {
                provider_ = value;
            }
        }

        [SetUp]
        public void SetUp()
        {
            Debugger.Launch();
            Provider = null;
            // Reset the file before running test
            string sourceCodeBase = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
            string sourceCodeBaseDirectory = Path.GetDirectoryName(sourceCodeBase);
            string sourceFilePath = Path.Combine(sourceCodeBaseDirectory, "clean.monengine4net.xml");

            string targetCodeBase = new Uri(typeof(FileMonengineProvider.FileMonengineProvider).Assembly.CodeBase).LocalPath;
            string targetCodeBaseDirectory = Path.GetDirectoryName(targetCodeBase);
            string targetFilePath = Path.Combine(targetCodeBaseDirectory, "monengine4net.xml");
            File.Copy(sourceFilePath, targetFilePath, true);
        }

        [TearDown]
        public void TearDown()
        {
            Validate_XMLFile_Format();
        }

        [Test]
        public void Validate_XMLFile_Format()
        {
            XmlDocument document = new XmlDocument();
            string codeBaseDirectory = Path.GetDirectoryName(typeof(FileMonengineProvider.FileMonengineProvider).Assembly.CodeBase);
            document.Load(Path.Combine(codeBaseDirectory, "monengine4net.xml"));
            document.Schemas.Add(null, Path.Combine(codeBaseDirectory, "monengine4net.xsd"));
            document.Validate(HandleValidationEvent);
        }

        private void HandleValidationEvent(object src, ValidationEventArgs e)
        {
            Assert.Fail(e.Message);
        }

        [Test]
        public void FileProvider_CreateAccount_AlreadyExists()
        {
            try
            {
                Provider.CreateAccount(1, AccountType.ASSET);
                Provider.CreateAccount(1, AccountType.ASSET);
                Assert.Fail("Expected Exception");
            }
            catch
            {
                //
            }
        }

        [Test]
        public void FileProvider_CreateAccount_Success()
        {
            IAccount account = Provider.CreateAccount(int.MaxValue, AccountType.INCOME);
            account.Name = "Test Account";
            account.Description = "Test Account Description";
            Assert.AreEqual(int.MaxValue, account.Number);
            Assert.AreEqual(AccountType.INCOME, account.Type);
            Assert.AreEqual("Test Account", account.Name);
            Assert.AreEqual("Test Account Description", account.Description);
        }

        [Test]
        public void FileProvider_GetAccount()
        {
            IAccount account = Provider.CreateAccount(int.MaxValue, AccountType.INCOME);
            Assert.AreEqual(int.MaxValue, account.Number);
            Assert.AreEqual(AccountType.INCOME, account.Type);
            account.Name = "Test Account";
            account.Description = "Test Account Description";
            IAccount newAccount = Provider.GetAccount(account.Number);
            Assert.AreEqual(account.Number, newAccount.Number);
            Assert.AreEqual(account.Name, newAccount.Name);
            Assert.AreEqual(account.Description, newAccount.Description);
            Assert.AreEqual(account.Balance, newAccount.Balance);
        }

        [Test]
        public void FileProvider_GetAccounts()
        {
            IList<IAccount> accounts = Provider.GetAccounts();
            Assert.AreEqual(4, accounts.Count);
        }

        [Test]
        public void FileProvider_GetAccounts_Limit()
        {
            IList<IAccount> accounts = Provider.GetAccounts(1, 2);
            Assert.AreEqual(2, accounts.Count);
        }

        [Test]
        public void FileProvider_CreateAction()
        {
            IAccount account = Provider.CreateAccount(5, AccountType.ASSET);
            IAction action = Provider.CreateAction(ActionType.CREDIT, account, new Decimal(10));
        }

        [Test]
        public void FileProvider_CreateTransaction()
        {

            // Setup data
            IAccount bankAccount1 = Provider.CreateAccount(11, AccountType.ASSET);
            IAccount creditorAccount2 = Provider.CreateAccount(12, AccountType.LIABILITY);
            IAccount incomeAccount3 = Provider.CreateAccount(13, AccountType.INCOME);
            IAccount expenseAccount4 = Provider.CreateAccount(14, AccountType.EXPENSE);

            // Perform actions
            // First initialize the asset and liability accounts
            // Deposit 100.25 in the bank account
            bankAccount1.Debit(new Decimal(100.25), null);
            Assert.AreEqual(100.25, bankAccount1.Balance);

            // We owe creditor 50.75 say
            creditorAccount2.Credit(new Decimal(50.75), null);
            Assert.AreEqual(50.75, creditorAccount2.Balance);

            // Now inside a transaction we make 20.95 and deposit that to our bank account
            ITransaction transaction = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction.Add(Provider.CreateAction(ActionType.CREDIT, incomeAccount3, new Decimal(20.95)));
            transaction.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            Assert.AreEqual(0, incomeAccount3.Balance);
            Assert.AreEqual(100.25, bankAccount1.Balance);

            transaction.Commit();
            Assert.AreEqual(20.95, incomeAccount3.Balance);
            Assert.AreEqual(121.20, bankAccount1.Balance);

        }

        [Test]
        public void FileProvider_GetTransactions_All()
        {
            // Setup data
            IAccount bankAccount1 = Provider.CreateAccount(11, AccountType.ASSET);
            IAccount creditorAccount2 = Provider.CreateAccount(12, AccountType.LIABILITY);
            ITransaction transaction = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount2, new Decimal(20.95)));
            ITransaction transaction2 = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction2.Add(Provider.CreateAction(ActionType.CREDIT, bankAccount1, new Decimal(20.95)));
            transaction2.Add(Provider.CreateAction(ActionType.DEBIT, creditorAccount2, new Decimal(20.95)));

            IList<ITransaction> transactions = Provider.GetTransactions();
            // The clean file contains 2 transactions already
            Assert.AreEqual(2, transactions.Count);
            transaction.Commit();
            transactions = Provider.GetTransactions();
            Assert.AreEqual(3, transactions.Count);
            transaction2.Commit();
            transactions = Provider.GetTransactions();
            Assert.AreEqual(4, transactions.Count);
        }

        [Test]
        public void FileProvider_GetTransactions_For_Account()
        {
            // Setup data
            IAccount bankAccount1 = Provider.CreateAccount(11, AccountType.ASSET);
            IAccount creditorAccount2 = Provider.CreateAccount(12, AccountType.LIABILITY);
            IAccount creditorAccount3 = Provider.CreateAccount(13, AccountType.LIABILITY);
            ITransaction transaction = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount2, new Decimal(20.95)));
            transaction.Commit();
            ITransaction transaction2 = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction2.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction2.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount3, new Decimal(20.95)));
            transaction2.Commit();

            List<ITransaction> transactions = new List<ITransaction>(Provider.GetTransactions(creditorAccount3));
            Assert.AreEqual(1, transactions.Count);
            Assert.IsTrue(transactions.Exists(
                    delegate(ITransaction trans)
                    {
                        List<IAction> actions = new List<IAction>(trans);
                        List<IAction> accountActions = actions.FindAll(
                            delegate(IAction action)
                            {
                                return action.Account.Number == creditorAccount3.Number;
                            }
                        );
                        return accountActions.Count == 1;
                    }
                )
            );
        }

        [Test]
        public void FileProvider_GetTransactions_For_Account_After()
        {
            // Setup data
            IAccount bankAccount1 = Provider.CreateAccount(11, AccountType.ASSET);
            IAccount creditorAccount2 = Provider.CreateAccount(12, AccountType.LIABILITY);
            IAccount creditorAccount3 = Provider.CreateAccount(13, AccountType.LIABILITY);
            ITransaction transaction = Provider.CreateTransaction(DateTimeOffset.Now);
            transaction.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount2, new Decimal(20.95)));
            transaction.Commit();
            ITransaction transaction2 = Provider.CreateTransaction(transaction.Timestamp.AddMilliseconds(1));
            DateTimeOffset afterTransaction2 = transaction2.Timestamp.AddMilliseconds(1);
            transaction2.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction2.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount3, new Decimal(20.95)));
            transaction2.Commit();
            ITransaction transaction3 = Provider.CreateTransaction(afterTransaction2.AddMilliseconds(1));
            transaction3.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction3.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount3, new Decimal(20.95)));
            transaction3.Commit();
            List<ITransaction> transactions = new List<ITransaction>(Provider.GetTransactions(creditorAccount3, afterTransaction2));
            Assert.AreEqual(1, transactions.Count, "after: {0}\ntransaction 3: {1}", afterTransaction2, transaction3.Timestamp);
            Assert.IsTrue(transactions.Exists(
                    delegate(ITransaction trans)
                    {
                        List<IAction> actions = new List<IAction>(trans);
                        List<IAction> accountActions = actions.FindAll(
                            delegate(IAction action)
                            {
                                return action.Account.Number == creditorAccount3.Number;
                            }
                        );
                        return accountActions.Count == 1;
                    }
                )
            );
        }

        [Test]
        public void FileProvider_GetTransactions_For_Account_After_And_Before()
        {
            // Setup data
            IAccount bankAccount1 = Provider.CreateAccount(11, AccountType.ASSET);
            IAccount creditorAccount2 = Provider.CreateAccount(12, AccountType.LIABILITY);
            IAccount creditorAccount3 = Provider.CreateAccount(13, AccountType.LIABILITY);
            DateTimeOffset startTime = DateTimeOffset.Now;
            DateTimeOffset transaction1Time = startTime.AddMilliseconds(1);
            ITransaction transaction = Provider.CreateTransaction(transaction1Time);
            transaction.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount2, new Decimal(20.95)));
            transaction.Commit();
            DateTimeOffset afterTransaction1 = transaction.Timestamp.AddMilliseconds(1);
            DateTimeOffset transaction2Time = transaction1Time.AddMilliseconds(1);
            ITransaction transaction2 = Provider.CreateTransaction(transaction2Time);
            transaction2.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction2.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount3, new Decimal(20.95)));
            transaction2.Commit();
            DateTimeOffset transaction3Time = transaction2Time.AddMilliseconds(1);
            ITransaction transaction3 = Provider.CreateTransaction(transaction3Time);
            transaction3.Add(Provider.CreateAction(ActionType.DEBIT, bankAccount1, new Decimal(20.95)));
            transaction3.Add(Provider.CreateAction(ActionType.CREDIT, creditorAccount3, new Decimal(20.95)));
            transaction3.Commit();

            List<ITransaction> transactions = new List<ITransaction>(Provider.GetTransactions(bankAccount1, afterTransaction1, transaction3Time));
            Assert.AreEqual(1, transactions.Count, "after: {0}\nbefore: {1}", afterTransaction1, transaction3Time);
            Assert.AreEqual(1, transactions.FindAll(
                    delegate(ITransaction trans)
                    {
                        List<IAction> actions = new List<IAction>(trans);
                        List<IAction> accountActions = actions.FindAll(
                            delegate(IAction action)
                            {
                                return action.Account.Number == bankAccount1.Number;
                            }
                        );
                        return accountActions.Count == 1;
                    }
                ).Count
            );
        }

        [Test]
        public void FileProvider_StoreRetrieveAndCompareImage()
        {
            var fileProvider = (FileMonengineProvider.FileMonengineProvider)Provider;
            var image = new ImageProducer(new Size(30, 30), Color.Blue).Image;
            XmlDocument document = new XmlDocument();
            XmlElement root = document.CreateElement("root");
            document.AppendChild(root);
            XmlElement node = XmlUtility.CreateImageElement(document.DocumentElement, "image", image);
            var imageRead = XmlUtility.ReadImageElement(node);
            byte[] imageBytes;
            byte[] imageReadBytes;
            using (var stream = new MemoryStream())
            {
                image.Save(stream, ImageFormat.Png);
                imageBytes = stream.ToArray();
            }
            using (var stream = new MemoryStream())
            {
                imageRead.Save(stream, ImageFormat.Png);
                imageReadBytes = stream.ToArray();
            }
            Assert.AreEqual(imageBytes, imageReadBytes);
            //PictureForm form = new PictureForm(imageRead);
            //form.ShowDialog();
        }

        private class ImageProducer
        {
            Image image_;
            public ImageProducer(Size size, Color color)
            {
                image_ = new Bitmap(size.Width, size.Height);
                using (var g = Graphics.FromImage(image_))
                {
                    var pen = new Pen(color, size.Height/10);
                    g.DrawLine(pen, new Point(0, 0), new Point(size.Width, size.Height));
                }
            }

            public Image Image
            {
                get
                {
                    return image_;
                }
            }
        }
    }
}
